using System;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics.Arm;
using System.Runtime.Versioning;

namespace ARMeilleure.CodeGen.Arm64
{
    static partial class HardwareCapabilities
    {
        static HardwareCapabilities()
        {
            if (!ArmBase.Arm64.IsSupported)
            {
                return;
            }

            if (OperatingSystem.IsLinux())
            {
                LinuxFeatureInfoHwCap = (LinuxFeatureFlagsHwCap)getauxval(AT_HWCAP);
                LinuxFeatureInfoHwCap2 = (LinuxFeatureFlagsHwCap2)getauxval(AT_HWCAP2);
            }

            if (OperatingSystem.IsMacOS())
            {
                for (int i = 0; i < _sysctlNames.Length; i++)
                {
                    if (CheckSysctlName(_sysctlNames[i]))
                    {
                        MacOsFeatureInfo |= (MacOsFeatureFlags)(1 << i);
                    }
                }
            }
        }

#region Linux

        private const ulong AT_HWCAP = 16;
        private const ulong AT_HWCAP2 = 26;

        [LibraryImport("libc", SetLastError = true)]
        private static partial ulong getauxval(ulong type);

        [Flags]
        public enum LinuxFeatureFlagsHwCap : ulong
        {
            Fp        = 1 << 0,
            Asimd     = 1 << 1,
            Evtstrm   = 1 << 2,
            Aes       = 1 << 3,
            Pmull     = 1 << 4,
            Sha1      = 1 << 5,
            Sha2      = 1 << 6,
            Crc32     = 1 << 7,
            Atomics   = 1 << 8,
            FpHp      = 1 << 9,
            AsimdHp   = 1 << 10,
            CpuId     = 1 << 11,
            AsimdRdm  = 1 << 12,
            Jscvt     = 1 << 13,
            Fcma      = 1 << 14,
            Lrcpc     = 1 << 15,
            DcpOp     = 1 << 16,
            Sha3      = 1 << 17,
            Sm3       = 1 << 18,
            Sm4       = 1 << 19,
            AsimdDp   = 1 << 20,
            Sha512    = 1 << 21,
            Sve       = 1 << 22,
            AsimdFhm  = 1 << 23,
            Dit       = 1 << 24,
            Uscat     = 1 << 25,
            Ilrcpc    = 1 << 26,
            FlagM     = 1 << 27,
            Ssbs      = 1 << 28,
            Sb        = 1 << 29,
            Paca      = 1 << 30,
            Pacg      = 1UL << 31
        }

        [Flags]
        public enum LinuxFeatureFlagsHwCap2 : ulong
        {
            Dcpodp      = 1 << 0,
            Sve2        = 1 << 1,
            SveAes      = 1 << 2,
            SvePmull    = 1 << 3,
            SveBitperm  = 1 << 4,
            SveSha3     = 1 << 5,
            SveSm4      = 1 << 6,
            FlagM2      = 1 << 7,
            Frint       = 1 << 8,
            SveI8mm     = 1 << 9,
            SveF32mm    = 1 << 10,
            SveF64mm    = 1 << 11,
            SveBf16     = 1 << 12,
            I8mm        = 1 << 13,
            Bf16        = 1 << 14,
            Dgh         = 1 << 15,
            Rng         = 1 << 16,
            Bti         = 1 << 17,
            Mte         = 1 << 18,
            Ecv         = 1 << 19,
            Afp         = 1 << 20,
            Rpres       = 1 << 21,
            Mte3        = 1 << 22,
            Sme         = 1 << 23,
            Sme_i16i64  = 1 << 24,
            Sme_f64f64  = 1 << 25,
            Sme_i8i32   = 1 << 26,
            Sme_f16f32  = 1 << 27,
            Sme_b16f32  = 1 << 28,
            Sme_f32f32  = 1 << 29,
            Sme_fa64    = 1 << 30,
            Wfxt        = 1UL << 31,
            Ebf16       = 1UL << 32,
            Sve_Ebf16   = 1UL << 33,
            Cssc        = 1UL << 34,
            Rprfm       = 1UL << 35,
            Sve2p1      = 1UL << 36
        }

        public static LinuxFeatureFlagsHwCap LinuxFeatureInfoHwCap { get; } = 0;
        public static LinuxFeatureFlagsHwCap2 LinuxFeatureInfoHwCap2 { get; } = 0;

#endregion

#region macOS

        [LibraryImport("libSystem.dylib", SetLastError = true)]
        private static unsafe partial int sysctlbyname([MarshalAs(UnmanagedType.LPStr)] string name, out int oldValue, ref ulong oldSize, IntPtr newValue, ulong newValueSize);

        [SupportedOSPlatform("macos")]
        private static bool CheckSysctlName(string name)
        {
            ulong size = sizeof(int);
            if (sysctlbyname(name, out int val, ref size, IntPtr.Zero, 0) == 0 && size == sizeof(int))
            {
                return val != 0;
            }
            return false;
        }

        private static string[] _sysctlNames = new string[]
        {
            "hw.optional.floatingpoint",
            "hw.optional.AdvSIMD",
            "hw.optional.arm.FEAT_FP16",
            "hw.optional.arm.FEAT_AES",
            "hw.optional.arm.FEAT_PMULL",
            "hw.optional.arm.FEAT_LSE",
            "hw.optional.armv8_crc32",
            "hw.optional.arm.FEAT_SHA1",
            "hw.optional.arm.FEAT_SHA256"
        };

        [Flags]
        public enum MacOsFeatureFlags
        {
            Fp      = 1 << 0,
            AdvSimd = 1 << 1,
            Fp16    = 1 << 2,
            Aes     = 1 << 3,
            Pmull   = 1 << 4,
            Lse     = 1 << 5,
            Crc32   = 1 << 6,
            Sha1    = 1 << 7,
            Sha256  = 1 << 8
        }

        public static MacOsFeatureFlags MacOsFeatureInfo { get; } = 0;

#endregion

        public static bool SupportsAdvSimd => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Asimd) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.AdvSimd);
        public static bool SupportsAes => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Aes) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.Aes);
        public static bool SupportsPmull => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Pmull) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.Pmull);
        public static bool SupportsLse => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Atomics) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.Lse);
        public static bool SupportsCrc32 => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Crc32) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.Crc32);
        public static bool SupportsSha1 => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Sha1) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.Sha1);
        public static bool SupportsSha256 => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Sha2) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.Sha256);
    }
}
